/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include <aos/aos.h>
#include "drv_display.h"
#include "lscreen.h"
#include "gif_decoder.h"
#include "voice_fft.h"
#include "lscreen_stream/stream.h"
#include <math.h>

#define TAG "lscreen"

/* ascii */
#define LED_ASCII_BEGIN     ' '
#define LED_ASCII_END       '~'

/* FFT */
#define FFT_BUFFER_LEN          256
#define FFT_SPECTRUM_POINT      (FFT_BUFFER_LEN + 1)

/* state event */
#define EVT_VOICE_FFT_QUIT  1

/*
 * F: need space between chars
 * N: don't need space between chars
*/
const static char *map_5x3[] = {
    "000000000000000N",      // ' '
    "010010010000010N",      // '!'
    "101101000000000F",      //
    "101111101111101F",      //
    "010111010111010F",      //
    "100001010100001F",      //
    "010001110101010F",      //
    "010010000000000N",      //
    "010100100100010F",      //
    "010001001001010F",      //
    "000010101010000F",      //
    "000010111010000F",      //
    "000000000010010N",      //
    "000000111000000F",      //
    "000000000010000N",      //
    "000001010100000F",      //
    "111101101101111F",      //
    "110010010010111F",      //
    "111001111100111F",      //
    "111001011001111F",      //
    "101101111001001F",      //
    "111100111001111F",      //
    "111100111101111F",      //
    "111001001001001F",      //
    "111101111101111F",      //
    "111101111001111F",      //
    "000010000010000N",      //
    "000010000010010N",      //
    "001010100010001F",      //
    "000111000111000F",      //
    "100010001010100F",      //
    "111001010000010F",      //
    "111101101100111F",      //
    "010101111101101F",      //
    "110101110101111F",      //
    "111100100100111F",      //
    "110101101101110F",      //
    "111100111100111F",      //
    "111100111100100F",      //
    "111100101101111F",      //
    "101101111101101F",      //
    "111010010010111F",      //
    "111010010010110F",      //
    "100111100110101F",      //
    "100100100100111F",      //
    "101111111101101F",      //
    "111101101101101F",      //
    "010101101101010F",      //
    "111101111100100F",      //
    "111101111001001F",      //
    "100111100100100F",      //
    "111100010001111F",      //
    "111010010010010F",      //
    "101101101101111F",      //
    "101101101101010F",      //
    "101101111111101F",      //
    "101101010101101F",      //
    "101101101010010F",      //
    "111001010100111F",      //
    "110100100100110F",      //
    "000100010001000F",      //
    "011001001001011F",      //
    "010101000000000F",      //
    "000000000000111F",      //
    "010001000000000F",      //
    "111101111101101F",      //
    "100100111101111F",      //
    "111101100100111F",      //
    "001001111101111F",      //
    "111101111100111F",      //
    "111100110100100F",      //
    "111101111001011F",      //
    "100100111101101F",      //
    "010000010010010N",      //
    "010000010010110F",      //
    "100101110101101F",      //
    "010010010010011F",      //
    "000101111101101F",      //
    "000111101101101F",      //
    "000010101101010F",      //
    "000111101111100F",      //
    "000111101111001F",      //
    "000100110100100F",      //
    "011100010001110F",      //
    "010111010010011F",      //
    "000101101101111F",      //
    "000101101101010F",      //
    "000101101111101F",      //
    "000101010101000F",      //
    "101101111001111F",      //
    "000111010100111F",      //
    "001010110010001F",      //
    "010010010010010N",      //
    "100010011010100F",      //
    "000001111100000F",      //
};

typedef struct {
    const char name[8];           // "5x3"
    const char **map;
    uint8_t *cache;
    int map_row;
    int map_col;
    int pix_row;
    int pix_col;
    // int size;           // height * width
} ascii_map_t;

typedef struct {
    aos_mutex_t lock;
    aos_mutex_t evt_state;
    int tsk_running;
    int fft_upate;
    int update_period;          // update time
    int update_cnt;             // upate when update_cnt == update_period
    int cur_vol;                // current volume for adjusting max val 
    float *spectrum;
    int spectrum_len;
    uint8_t last_freqs[17];     // remember last freqs result for led afterimage
    float fft_buffer[FFT_BUFFER_LEN];
} voice_fft_t;

struct _led_screen_t {
    aos_dev_t *dev;
    int height;
    int width;
    int size;                       // size = width * height
    uint8_t *frame_buf;
    uint8_t *pix_pos[2];            // 2 * width for mapping from led matrix to asccii map
    ascii_map_t *ascii_map;
    voice_fft_t *fft_config;
};

// typedef enum {
//     ASCII_NONE_SPACE = 0,
//     ASCII_NEED_SPACE,
// } ascii_type_e;

static ascii_map_t g_ascii_maps[1] = {
    {
        "5x3",
        map_5x3,
        NULL,
        sizeof(map_5x3) / sizeof(char *),
        3 * 5 + 1,
        5,
        3
    }
};

/* use a 2 * LED_SCREEN_WIDTH array to store the map index for the screen */
static int translate_str(led_screen_t *hdl, const char *str, uint8_t *pix_pos[2])
{
    CHECK_PARAM(str && hdl && hdl->pix_pos[0] && hdl->pix_pos[1], -1);

    int str_len = strlen(str);
    int char_pos = 0;
    int width = hdl->width;
    int pix_col = hdl->ascii_map->pix_col;

    memset(hdl->pix_pos[0], 0, hdl->width);
    memset(hdl->pix_pos[1], 0, hdl->width);

    for (int row = 0; row < width && char_pos < str_len; char_pos++) {
        int fill_row = 0;
        int map_index = str[char_pos] - LED_ASCII_BEGIN;

        /* fill pixel pos according to character type */
        if (hdl->ascii_map->map[map_index][hdl->ascii_map->map_col - 1] == 'F' && (row + pix_col <= width)) {
            int next_map_index = str[char_pos + 1] - LED_ASCII_BEGIN;

            /* if next char is a non-space character, don't reserve space */
            if (char_pos + 1 < str_len && hdl->ascii_map->map[next_map_index][hdl->ascii_map->map_col - 1] == 'N') {
                fill_row = pix_col;
            } else {
                fill_row = row + pix_col == width ? pix_col : pix_col + 1;
            }
        } else if (row + pix_col - 1 <= width) {
            fill_row = row + pix_col - 1 < width ? pix_col : pix_col - 1;
        }

        for (int i = 0; i < fill_row; i++) {
            /* pix_pos[0] is the char index in the acii map, pix_pos[1] is the row of the current char pixel */
            if (i == 3) {
                pix_pos[0][row]   = 0;
                pix_pos[1][row]   = 0;                
            } else {
                pix_pos[0][row]   = map_index;
                pix_pos[1][row]   = i;
            }
            row++;
        }
    }

    return 0;
}

static int lscreen_ascii_convert(led_screen_t *hdl, const char *str, uint8_t *screen_buf, int start_col, int start_row, int brt)
{
    CHECK_PARAM(str && screen_buf && hdl && hdl->ascii_map, -1);

    int ret = translate_str(hdl, str, hdl->pix_pos);
    CHECK_RET_TAG_WITH_RET(ret == 0, -1);
    
    /* use the map index to convert the string to screen buffer array */
    for (int i = start_row; i < hdl->height; i++) {
        for (int j = start_col; j < hdl->width; j++) {
            int map_row = hdl->pix_pos[0][j - start_col];
            int map_col = hdl->ascii_map->pix_col * (i - start_row) + hdl->pix_pos[1][j - start_col];

            screen_buf[i * hdl->width + j] = hdl->ascii_map->map[map_row][map_col] == '1' ? brt : 0;
        }
    }
    return 0;
}

/**
 * 获取转换后fft数据
 *
 * @param max 转换后FFT最高值
 * @param freq_num 转换后FFT频率个数
 * @param [out] freqs 转换后FFT数据 
 * @return 0:成功
 */
static int voice_fft_get(voice_fft_t *fft, uint8_t *freqs, int freq_num, int max)
{
    CHECK_PARAM(freqs && freq_num > 0 && max > 0, -1);

    /* divide the freqency according to log distribution */
    int freq_ranges[] = {1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 7, 9, 10, 12, 14};
    int freq_sum = 86;

    /* 由于高频部分幅值较低，将以上频率范围等分为五个频段，分别乘以不同系数以增强显示效果 */
    const int16_t part_scaling_factor[5] = {25, 50, 100, 200, 300};
    int part_num = (freq_sum + 1) / 5 + ((freq_sum + 1) % 5 ? 1 : 0);


    int freqs_tmp[FFT_BUFFER_LEN];
    int cur_freq_pos = 1;           // drop the very low frequency range
    uint8_t freq_max = 0;
    uint8_t freq_min = 255;

    int point_min = 65536, point_max = -65536;
    int freq_val;

    aos_mutex_lock(&fft->lock, AOS_WAIT_FOREVER);

    for (int i = 0; i < FFT_BUFFER_LEN; i++) {
        point_min = MIN((int)fft->fft_buffer[i], point_min);
        point_max = MAX((int)fft->fft_buffer[i], point_max);
    }
    gen_smooth_power_spectrum(fft->fft_buffer, fft->spectrum);
    aos_mutex_unlock(&fft->lock);

    /* filter the small noise */
    point_max = MAX(abs(point_min), point_max);
    if (point_max < 30) {
        memset(freqs, 0, freq_num);
        return 0;
    }

    for (int i = 0; i < freq_num; i++) {
        long long sum = 0;
        for (int j = 0; j < freq_ranges[i]; j++, cur_freq_pos++) {
            sum += (int)(fft->spectrum[cur_freq_pos]) * part_scaling_factor[cur_freq_pos / part_num] / 100;
        }

        freqs_tmp[i] = sum / freq_ranges[i];

        freq_val = ls_fast_log(freqs_tmp[i]);
        freqs[i] = freq_val < 0 ? 0 : freq_val;

        freq_max = MAX(freqs[i], freq_max);
        freq_min = MIN(freqs[i], freq_min);
    }

    /* 防止满屏 */
    if (freq_max - freq_min > 7) {
        freq_min += (freq_max - freq_min - 7);
    }
    freq_min = MAX(freq_min, 4);            // drop very small voice

    for (int i = 0; i < freq_num; i++) {
        freqs[i] = freqs[i] - freq_min <= 0 ? 0 : freqs[i] - freq_min;
    }

    return 0;
}

/* LED FFT显示任务，每隔150 ms根据当前语音FFT值，计算刷新一下LED显示 */
static void led_fft_tsk(void *arg)
{
    led_screen_t *screen = (led_screen_t *)arg;

    uint8_t *freqs = (uint8_t *)aos_malloc_check(screen->width);
    uint8_t *led_col = (uint8_t *)aos_zalloc_check(screen->height);
    voice_fft_t *config = screen->fft_config;

    /* 打开PCM数据流 */
    // voice_fft_pcm_enable();

    while (config->tsk_running) {
        aos_msleep(config->update_period);

        if (!config->fft_upate) {
            /* 暂停，清除屏幕内容，等待继续 */
            lscreen_clear(screen);
            lscreen_flush(screen);
            while (!config->fft_upate && config->tsk_running) {
                aos_msleep(200);
            }
            continue;
        }

        /* 获取屏幕宽度个频率值，每个值最大为屏幕高度 */
        voice_fft_get(config, freqs, screen->width, screen->height);

        /* 将每个频率值，转化为屏幕上一个对应能量柱（大小0~height） */
        for (int i = 0; i < screen->width; i++) {
            for (int j = screen->height - 1; j >= 0; j--) {
                if (freqs[i] >= screen->height - j) {
                    led_col[j] = 0xff;
                } else {
                    /*  1. LED最下面一行低亮显示
                        2. last_freqs为上一次刷新频率值，用以产生余辉效果 
                    */
                    if (j == screen->height - 1 || screen->fft_config->last_freqs[i] >= screen->height - j) {
                        led_col[j] = 0x30;
                    } else {
                        led_col[j] = 0;
                    }
                }
            }
            lscreen_show(screen, i, 0, led_col, 1, screen->height);
        }
        lscreen_flush(screen);

        memcpy(config->last_freqs, freqs, sizeof(freqs));
    }

    /* 关闭PCM数据流 */
    // voice_fft_pcm_disable();

    /* 清空屏幕 */
    lscreen_clear(screen);
    lscreen_flush(screen);

    aos_free(led_col);
    aos_free(freqs);

    aos_event_set(&config->evt_state, EVT_VOICE_FFT_QUIT, AOS_EVENT_OR);
}

int lscreen_rhythm_data_update(led_screen_t *hdl, int16_t *data, size_t len, int cur_vol)
{
    CHECK_PARAM(hdl, -1);

    if (hdl->fft_config->fft_upate) {
        if (cur_vol >= 0 && cur_vol <= 100) {
            hdl->fft_config->cur_vol = cur_vol;
        }

        aos_mutex_lock(&hdl->fft_config->lock, AOS_WAIT_FOREVER);
        for(int i = 0; i < len && i < FFT_BUFFER_LEN; i++) {
            hdl->fft_config->fft_buffer[i] = data[i];
        }
        aos_mutex_unlock(&hdl->fft_config->lock);
    }

    return 0;
}

int lscreen_rhythm_start(led_screen_t *hdl, int update_interval)
{
    CHECK_PARAM(hdl && update_interval > 0, -1);

    int ret;
    
    if (!hdl->fft_config) {
        hdl->fft_config = (voice_fft_t *)aos_zalloc_check(sizeof(voice_fft_t));

        hdl->fft_config->update_cnt = 0;
        hdl->fft_config->spectrum_len = FFT_SPECTRUM_POINT * 2;                  // power spectrum generates FFT_SPECTRUM_POINT points, but need double space for calculation
        hdl->fft_config->spectrum = (float *)aos_zalloc_check(hdl->fft_config->spectrum_len * sizeof(float));
        memset(hdl->fft_config->last_freqs, 0, sizeof(hdl->fft_config->last_freqs));

        ret = aos_event_new(&hdl->fft_config->evt_state, 0);
        CHECK_RET_WITH_RET(ret == 0, -1);

        ret = aos_mutex_new(&hdl->fft_config->lock);
        CHECK_RET_WITH_RET(ret == 0, -1);
    }

    if (hdl->fft_config->tsk_running) {
        LOGE(TAG, "fft already running");
        return -1;
    }

    hdl->fft_config->update_period = update_interval;

    memset(hdl->fft_config->last_freqs, 0, sizeof(hdl->fft_config->last_freqs));
    hdl->fft_config->fft_upate = 1;
    hdl->fft_config->tsk_running = 1;
    aos_event_set(&hdl->fft_config->evt_state, 0, AOS_EVENT_AND);

    ret = aos_task_new("led_fft", led_fft_tsk, hdl, 4096);
    if (ret != 0) {
        LOGE(TAG, "led_fft task start failed");
        hdl->fft_config->tsk_running = 0;
        return -1;
    }

    return 0;
}

int lscreen_rhythm_stop(led_screen_t *hdl)
{
    CHECK_PARAM(hdl, -1);

    if (hdl->fft_config->tsk_running) {
        hdl->fft_config->tsk_running = 0;

        unsigned int flags = 0;
        aos_event_get(&hdl->fft_config->evt_state, EVT_VOICE_FFT_QUIT, AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
    }

    return 0;
}

void lscreen_rhythm_pause(led_screen_t *hdl)
{
    CHECK_PARAM(hdl, );

    if (hdl->fft_config->tsk_running) {
        hdl->fft_config->fft_upate = 0;
    }
}

void lscreen_rhythm_resume(led_screen_t *hdl)
{
    CHECK_PARAM(hdl, );

    if (hdl->fft_config->tsk_running) {
        hdl->fft_config->fft_upate = 1;
    }
}

int lscreen_clear(led_screen_t *hdl)
{
    CHECK_PARAM(hdl, -1);

    memset(hdl->frame_buf, 0, hdl->size);
    return 0;
}

int lscreen_show(led_screen_t *hdl, int pos_col, int pos_row, uint8_t *buf, int width, int height)
{
    CHECK_PARAM(hdl && pos_row >= 0 && pos_col >= 0 && height >= 0 && width >= 0, -1);

    for (int i = 0; i < height && pos_row + i < hdl->height; i++) {
        for (int j = 0; j < width && pos_col + j < hdl->width; j++) {
            hdl->frame_buf[(pos_row + i) * hdl->width + pos_col + j] = buf[i * width + j];
        }
    }

    return 0;
}

int lscreen_play_gif(led_screen_t *hdl, const char *url)
{
    CHECK_PARAM(hdl && url, -1);

    gif_decoder_handler_t *gif_hdl = gif_new(url);
    CHECK_RET_TAG_WITH_RET(gif_hdl, -1);

    int height = gif_get_height(gif_hdl);
    int width = gif_get_width(gif_hdl);
    int ret;

    if (height <= 0 || width <= 0) {
        LOGE(TAG, "size error %d %d", height, width);
        return -1;
    }

    int delay_ms;

    while (1) {
        ret = gif_get_next_image(gif_hdl, hdl->frame_buf, NULL, NULL, &delay_ms);
        if (ret < 0) {
            break;
        }

        lscreen_flush(hdl);

        if (delay_ms > 0) {
            aos_msleep(delay_ms);
        }
    }

    // lscreen_clear(hdl);
    // lscreen_flush(hdl);

    gif_free(gif_hdl);
    return 0;
}

int lscreen_show_string(led_screen_t *hdl, int start_col, int start_row, const char *str, int on_val)
{
    CHECK_PARAM(hdl && str && on_val > 0 && on_val < 256, -1);

    return lscreen_ascii_convert(hdl, str, hdl->frame_buf, start_col, start_row, on_val);
}

int lscreen_show_ascii(led_screen_t *hdl, int pos_col, int pos_row, char c, int on_val)
{
    CHECK_PARAM(hdl && c >= LED_ASCII_BEGIN && c <= LED_ASCII_END && on_val > 0 && on_val < 256, -1);

    int map_index = c - LED_ASCII_BEGIN;
    int char_size = hdl->ascii_map->pix_col * hdl->ascii_map->pix_row;
    const char *pix = &hdl->ascii_map->map[map_index][0];
    uint8_t *data;

    if (!hdl->ascii_map->cache) {
        hdl->ascii_map->cache = (uint8_t *)aos_malloc_check(char_size);
    }
    data = hdl->ascii_map->cache;
    memset(data, 0, char_size);

    for (int i = 0; i < char_size; i++) {
        if (pix[i] == '1')
        data[i] = pix[i] == '1' ? on_val : 0;
    }

    return lscreen_show(hdl, pos_col, pos_row, data, hdl->ascii_map->pix_col, hdl->ascii_map->pix_row);
}

int lscreen_flush(led_screen_t *hdl)
{
    CHECK_PARAM(hdl && hdl->dev, -1);
    
    return mdisplay_show(hdl->dev, hdl->frame_buf, hdl->size);
}

int lscreen_enable(led_screen_t *hdl, int en)
{
    CHECK_PARAM(hdl && hdl->dev, -1);

    if (en) {
        return mdisplay_enable(hdl->dev);
    } else {
        return mdisplay_disable(hdl->dev);
    }
}

led_screen_t *lscreen_init(lscreen_config_t *config)
{
    led_screen_t *screen_hdl = (led_screen_t *)aos_zalloc_check(sizeof(led_screen_t));
    screen_hdl->dev = config->dev_disp;
    screen_hdl->height = config->ver_resolution;
    screen_hdl->width = config->hor_resolution;
    screen_hdl->ascii_map = &g_ascii_maps[0];
    screen_hdl->size = screen_hdl->height * screen_hdl->width;
    screen_hdl->frame_buf = (uint8_t *)aos_zalloc_check(screen_hdl->size);
    
    /* match for the ascii map */
    if (config->ascii_map_name) {
        for (int i = 0; i < sizeof(g_ascii_maps) / sizeof(ascii_map_t); i++) {
            if (strcmp(config->ascii_map_name, g_ascii_maps[0].name) == 0) {
                screen_hdl->ascii_map = &g_ascii_maps[i];
                LOGD(TAG, "find ascii map: %s", g_ascii_maps[i].name);
                break;
            }
        }
    }

    screen_hdl->pix_pos[0] = (uint8_t *)aos_zalloc_check(screen_hdl->width);
    screen_hdl->pix_pos[1] = (uint8_t *)aos_zalloc_check(screen_hdl->width);

    lstream_ops_register_all();

    return screen_hdl;
}

int lscreen_deinit(led_screen_t *hdl)
{
    CHECK_PARAM(hdl, -1);

    if (hdl->fft_config) {
        aos_mutex_free(&hdl->fft_config->lock);
        aos_event_free(&hdl->fft_config->evt_state);
        aos_free(hdl->fft_config->spectrum);
        hdl->fft_config = NULL;
    }

    aos_free(hdl->frame_buf);
    hdl->frame_buf = NULL;

    aos_free(hdl->pix_pos[0]);
    hdl->pix_pos[0] = NULL;

    aos_free(hdl->pix_pos[1]);
    hdl->pix_pos[1] = NULL;

    aos_free(hdl);
	return 0;
}

